
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author david
 */
public class AnalizadorLexico
{
    private enum Estado
    {
	INICIAL
	{
	    @Override
	    public Estado delta(int c)
	    {
		if (esDigito(c))
		{
		    return ENTERO;
		}
		else if (esLetra(c))
		{
		    return ID;
		}
		else if (esSeparador(c))
		{
		    return this;
		}
		else
		{
		    switch (c)
		    {
			case '(':
			    return PARI;
			case ')':
			    return PARD;
			case ';':
			    return PYC;
			case '=':
			    return ASIG;
			case '{':
			    return LLAVEI;
			case '}':
			    return LLAVED;
			case '/':
			    return INICIO_COMENTARIO_BARRA;
			case -1:
			    return EOF;
			default:
			    return null;
		    }
		}
	    }

	},
	// finales y aceptación
	PARI,
	PARD,
	PYC,
	ASIG,
	LLAVEI,
	LLAVED,
	FIN_COMENTARIO,
	EOF,
	// aceptación
	ID
	{
	    @Override
	    public Estado delta(int c)
	    {
		return (esLetra(c) || esDigito(c)) ? ID : null;
	    }

	},
	ENTERO
	{
	    @Override
	    public Estado delta(int c)
	    {
		return esDigito(c) ? ENTERO : c == '.' ? DOT : null;
	    }

	},
	REAL
	{
	    @Override
	    public Estado delta(int c)
	    {
		return esDigito(c) ? REAL : null;
	    }

	},
	// normales
	DOT
	{
	    @Override
	    public Estado delta(int c)
	    {
		return esDigito(c) ? REAL : null;
	    }

	},
	INICIO_COMENTARIO_BARRA
	{
	    @Override
	    public Estado delta(int c)
	    {
		return c == '*' ? INICIO_COMENTARIO_ASTERISCO : null;
	    }

	},
	INICIO_COMENTARIO_ASTERISCO
	{
	    @Override
	    public Estado delta(int c)
	    {
		return c == '*' ? FIN_COMENTARIO_ASTERISCO : c == -1 ? null :
							     this;
	    }

	},
	FIN_COMENTARIO_ASTERISCO
	{
	    @Override
	    public Estado delta(int c)
	    {
		return c == '/' ? FIN_COMENTARIO : c == '*' ? this :
						   c == -1 ? null :
						   INICIO_COMENTARIO_ASTERISCO;
	    }

	};

	public Estado delta(int c)
	{
	    return null;
	}

	private static boolean esDigito(int c)
	{
	    return c >= '0' && c <= '9';
	}

	private static boolean esLetra(int c)
	{
	    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
	}

    }
    private final EnumMap<Estado, Token.Tipo> tokenEstado;
    private final EnumSet<Estado> finales = EnumSet.range(Estado.PARI,
							  Estado.EOF);
    private final EnumSet<Estado> aceptacion = EnumSet.range(Estado.PARI,
							     Estado.REAL);
    private RandomAccessFile entrada;
    private int linea;
    private int columna;
    private static final char separadores[] =
    {
	' ', '\t', '\r', '\n'
    };

    public AnalizadorLexico(String filename)
    {
	linea = 1;
	columna = 1;
	try
	{
	    entrada = new RandomAccessFile(filename, "r");
	}
	catch (FileNotFoundException ex)
	{
	    Logger.getLogger(AnalizadorLexico.class.getName()).
	    log(Level.SEVERE, null, ex);
	}

	tokenEstado = new EnumMap<Estado, Token.Tipo>(Estado.class);

	// Hacer el mapeo de estados de aceptacion con tipos de token
	tokenEstado.put(Estado.PARI, Token.Tipo.PARI);
	tokenEstado.put(Estado.PARD, Token.Tipo.PARD);
	tokenEstado.put(Estado.PYC, Token.Tipo.PYC);
	tokenEstado.put(Estado.ASIG, Token.Tipo.ASIG);
	tokenEstado.put(Estado.LLAVEI, Token.Tipo.LLAVEI);
	tokenEstado.put(Estado.LLAVED, Token.Tipo.LLAVED);
	tokenEstado.put(Estado.ID, Token.Tipo.ID);
	tokenEstado.put(Estado.ENTERO, Token.Tipo.ENTERO);
	tokenEstado.put(Estado.REAL, Token.Tipo.REAL);
	tokenEstado.put(Estado.EOF, Token.Tipo.EOF);
	tokenEstado.put(Estado.FIN_COMENTARIO, Token.Tipo.COMENTARIO);
    }

    public Token siguienteToken() throws PLException
    {
	Token tok = null;
	Token.Tipo t = null;
	Estado s = null;
	Estado q = Estado.INICIAL;
	String l = "";
	String p = "";
	int c = 0;
	int col = -1;

	try
	{
	    do
	    {
		c = entrada.read();

		// El caracter de fin de fichero no entra en P
		if (c != -1)
		{
		    p += (char) c;
		}

		q = q.delta(c);

		if (q != null)
		{
		    if (q != Estado.INICIAL && col == -1)
		    {
			col = columna;
			p = p.replaceAll("[ \t\r\n]+", "");
		    }

		    if (aceptacion.contains(q))
		    {
			s = q;
			l += p;
			p = "";
		    }

		    if (c == '\n')
		    {
			linea++;
			columna = 0;
		    }
		}

		// Ni tampoco aumenta la columna
		if (c != -1)
		{
		    columna++;
		}
	    }
	    while (q != null && !finales.contains(q));

	    if (s != null)  // Tenemos un token
	    {
		entrada.seek(entrada.getFilePointer() - p.length());
		columna -= p.length();
		t = (tokenEstado.get(s) != Token.Tipo.ID ? tokenEstado.get(s) :
		     idToKeyword(l));
		tok = new Token(t, l, linea, col);
	    }
	    else	    // Error léxico
	    {
		if (c == '\n')
		{
		    c = p.charAt(p.length()-2);
		    columna--;
		}
		else if (c == -1 && p.equals("/"))
		{
		    c = '/';
		}

		mensajeErrorLexico(c, linea, columna);
	    }
	}
	catch (IOException e)
	{
	}

	return tok;
    }

    private void mensajeErrorLexico(int c, int linea, int columna) throws
    PLException
    {
	String msgErr = "Error ";

	if (c == -1)
	{
	    msgErr += "2: fin de fichero inesperado";
	}
	else
	{
	    msgErr += "1 (" + linea + "," + (columna - 1) + "): caracter '" +
		       (char) c + "' incorrecto";
	}

	throw new PLException(msgErr);
    }

    private Token.Tipo idToKeyword(String s)
    {
	if (s.equals("class"))
	{
	    return Token.Tipo.CLASS;
	}
	else if (s.equals("double"))
	{
	    return Token.Tipo.DOUBLE;
	}
	else if (s.equals("int"))
	{
	    return Token.Tipo.INT;
	}
	else if (s.equals("main"))
	{
	    return Token.Tipo.MAIN;
	}
	else if (s.equals("void"))
	{
	    return Token.Tipo.VOID;
	}
	else
	{
	    return Token.Tipo.ID;
	}
    }

    private static boolean esSeparador(int c)
    {
	boolean res = false;
	int i = 0;

	while (i < separadores.length && c != separadores[i])
	{
	    i++;
	}

	if (i < separadores.length)
	{
	    res = true;
	}

	return res;
    }

}
